`include "common.svh"
`define DISPATCH_DIFFTEST
`ifdef DISPATCH_DIFFTEST
import "DPI-C" function int dispatch_create(
  int id,
  string name,
  int width
);
import "DPI-C" function void dispatch_out(
  int id,
  byte ref_fu,
  byte fu,
  int rob_idx,
  longint pc
);
import "DPI-C" function void dispatch_in(
  int id,
  byte fu,
  int rob_idx,
  longint pc
);
`endif
module dispatch #(
    parameter DISPATCH_WIDTH = 2
) (
    input clk,
    input rst,

    input i_valid,
    input RenamedOP i_rop[DISPATCH_WIDTH-1:0],
    input i_rop_valid[DISPATCH_WIDTH-1:0],
    output i_ready,

    output PRF_IDX rf_raddr[2*DISPATCH_WIDTH-1:0],
    input word_t rf_rdata[2*DISPATCH_WIDTH-1:0],
    input rf_rdata_valid[2*DISPATCH_WIDTH-1:0],

    // TO ROB
    output o_rob_write_valid,
    output o_rob_write_op_valid[DISPATCH_WIDTH-1:0],
    output ROB_Entry o_rob_write_op[DISPATCH_WIDTH-1:0],
    input ROB_PTR o_rob_write_rob_idx[DISPATCH_WIDTH-1:0],
    input o_rob_write_ready,

    //TO ALU
    output o_alu_valid[DISPATCH_WIDTH-1:0],
    output ISSUE_OP o_alu_iop[DISPATCH_WIDTH-1:0],
    input o_alu_ready,
    //TO BRU
    output o_bru_valid[DISPATCH_WIDTH-1:0],
    output ISSUE_OP o_bru_iop[DISPATCH_WIDTH-1:0],
    input o_bru_ready,
    //TO LSU
    output o_lsu_valid[DISPATCH_WIDTH-1:0],
    output ISSUE_OP o_lsu_iop[DISPATCH_WIDTH-1:0],
    input o_lsu_ready,
    //TO MDU
    output o_mdu_valid[DISPATCH_WIDTH-1:0],
    output ISSUE_OP o_mdu_iop[DISPATCH_WIDTH-1:0],
    input o_mdu_ready,
    //TO MISC
    output o_misc_valid[DISPATCH_WIDTH-1:0],
    output ISSUE_OP o_misc_iop[DISPATCH_WIDTH-1:0],
    input o_misc_ready
);
  localparam IOP_SIZE = $size(ISSUE_OP);
  localparam DISPATCH_AW = $clog2(DISPATCH_WIDTH);

  FU_Type i_fu_type[DISPATCH_WIDTH-1:0];

  wire i_fire = i_valid & i_ready;

  logic [DISPATCH_WIDTH-1:0] i_alu_valid;
  logic [DISPATCH_WIDTH-1:0] i_bru_valid;
  logic [DISPATCH_WIDTH-1:0] i_lsu_valid;
  logic [DISPATCH_WIDTH-1:0] i_mdu_valid;
  logic [DISPATCH_WIDTH-1:0] i_misc_valid;

  logic [DISPATCH_WIDTH-1:0][DISPATCH_AW-1:0] i_alu_idx;
  logic [DISPATCH_WIDTH-1:0][DISPATCH_AW-1:0] i_bru_idx;
  logic [DISPATCH_WIDTH-1:0][DISPATCH_AW-1:0] i_lsu_idx;
  logic [DISPATCH_WIDTH-1:0][DISPATCH_AW-1:0] i_mdu_idx;
  logic [DISPATCH_WIDTH-1:0][DISPATCH_AW-1:0] i_misc_idx;
  logic i_alu_ready;
  logic i_bru_ready;
  logic i_lsu_ready;
  logic i_mdu_ready;
  logic i_misc_ready;

  logic [DISPATCH_AW:0] dispatch_alu_num;
  logic [DISPATCH_AW:0] dispatch_bru_num;
  logic [DISPATCH_AW:0] dispatch_lsu_num;
  logic [DISPATCH_AW:0] dispatch_mdu_num;
  logic [DISPATCH_AW:0] dispatch_misc_num;


  function logic [DISPATCH_AW:0] CountValidFuNum;
    input FU_Type_Bit fu_bit;
    logic [DISPATCH_AW:0] dout;
    integer i;
    begin
      dout = 'b0;
      for (i = 0; i < DISPATCH_WIDTH; i = i + 1) begin
        if (i_fire & i_rop_valid[i] & i_fu_type[i][fu_bit]) begin
          dout = dout + 'b1;
        end
      end
    end
    return dout;
  endfunction

  function logic [DISPATCH_AW-1:0] PriorIdxFinder;
    input [DISPATCH_AW-1:0] idx;
    input FU_Type_Bit fu_bit;
    logic [DISPATCH_AW:0] valid_cnt;
    logic [DISPATCH_AW-1:0] out;
    integer i;
    begin
      out = 'b0;
      valid_cnt = 'b0;
      for (i = 0; i < DISPATCH_WIDTH; i = i + 1) begin
        if (i_rop_valid[i] & i_fu_type[i][fu_bit]) begin
          if (valid_cnt == {1'b0, idx}) begin
            out = i[DISPATCH_AW-1:0];
          end
          valid_cnt = valid_cnt + 'b1;
        end
      end
    end
    return out;
  endfunction

  function logic [DISPATCH_WIDTH-1:0][DISPATCH_AW-1:0] CountFuIdx;
    input FU_Type_Bit fu_bit;
    logic [DISPATCH_WIDTH-1:0][DISPATCH_AW-1:0] idx;
    integer i;
    begin
      idx[0] = 'b0;
      for (i = 0; i < DISPATCH_WIDTH; i = i + 1) begin
        if (i_rop_valid[i] & i_fu_type[i][fu_bit]) begin
          idx[i] = idx[i-1] + 'b1;
        end else begin
          idx[i] = idx[i-1];
        end
      end
    end
    return idx;
  endfunction

  wire i_fu_ready = i_alu_ready & i_bru_ready & i_lsu_ready & i_mdu_ready & i_misc_ready;
  assign i_ready = i_fu_ready & o_rob_write_ready;
  assign o_rob_write_valid = i_valid & i_fu_ready;
  assign o_rob_write_op_valid = i_rop_valid;
  genvar gi;
  generate

    assign dispatch_alu_num  = CountValidFuNum(FU_ALU_BIT);
    assign dispatch_bru_num  = CountValidFuNum(FU_BRU_BIT);
    assign dispatch_lsu_num  = CountValidFuNum(FU_LSU_BIT);
    assign dispatch_mdu_num  = CountValidFuNum(FU_MDU_BIT);
    assign dispatch_misc_num = CountValidFuNum(FU_MISC_BIT);

    ISSUE_OP i_iop[DISPATCH_WIDTH-1:0];
    for (gi = 0; gi < DISPATCH_WIDTH; gi = gi + 1) begin : GenerateIOP
      assign rf_raddr[2*gi] = i_rop[gi].prs1;
      assign rf_raddr[2*gi+1] = i_rop[gi].prs2;
      assign o_rob_write_op[gi].rop = i_rop[gi];
      assign i_iop[gi].rop = i_rop[gi];
      assign i_iop[gi].rob_idx = o_rob_write_rob_idx[gi];

      always_comb begin
        case (i_rop[gi].fop.dop.src1_type)
          SRC_ZERO: i_iop[gi].src1 = 'b0;
          SRC_REG:  i_iop[gi].src1 = rf_rdata[2*gi];
          SRC_PC:   i_iop[gi].src1 = i_rop[gi].fop.pc;
          SRC_IMM:  i_iop[gi].src1 = `IMM_WORD(i_rop[gi].fop.dop.imm32);
          default:  i_iop[gi].src1 = 'b0;
        endcase

        case (i_rop[gi].fop.dop.src2_type)
          SRC_ZERO: i_iop[gi].src2 = 'b0;
          SRC_REG:  i_iop[gi].src2 = rf_rdata[2*gi+1];
          SRC_PC:   i_iop[gi].src2 = i_rop[gi].fop.pc;
          SRC_IMM:  i_iop[gi].src2 = `IMM_WORD(i_rop[gi].fop.dop.imm32);
          default:  i_iop[gi].src2 = 'b0;
        endcase
      end

      // assign i_iop[gi].src1 = rf_rdata[2*gi];
      // assign i_iop[gi].src2 = rf_rdata[2*gi+1];


      assign i_iop[gi].src1_valid = i_rop[gi].fop.dop.src1_type == SRC_REG ? rf_rdata_valid[2*gi] : 1'b1;
      assign i_iop[gi].src2_valid = i_rop[gi].fop.dop.src2_type == SRC_REG ? rf_rdata_valid[2*gi+1] : 1'b1;

      assign i_iop[gi].src1_wakeup = 'b0;
      assign i_iop[gi].src2_wakeup = 'b0;
    end
    for (gi = 0; gi < DISPATCH_WIDTH; gi = gi + 1) begin : DispatchFu
      ISSUE_OP i_alu_iop, i_bru_iop, i_lsu_iop, i_mdu_iop, i_misc_iop;
      assign i_fu_type[gi] = i_rop[gi].fop.dop.fu_type;

      //ALU
      assign i_alu_valid[gi] = gi < dispatch_alu_num;
      assign i_alu_idx[gi] = PriorIdxFinder(gi, FU_ALU_BIT);
      assign i_alu_iop = i_iop[i_alu_idx[gi]];

      wire i_alu_fire = i_alu_valid[gi] & i_alu_ready;
      wire o_alu_fire = o_alu_valid[gi] & o_alu_ready;
      reg_lr #(1) o_alu_valid_r (
          .load(i_alu_fire | o_alu_fire),
          .din (i_alu_fire),
          .dout(o_alu_valid[gi]),
          .*
      );
      reg_l #(IOP_SIZE) o_alu_iop_r (
          .load(i_alu_fire),
          .din (i_alu_iop),
          .dout(o_alu_iop[gi]),
          .*
      );
      //BRU
      assign i_bru_valid[gi] = gi < dispatch_bru_num;
      assign i_bru_idx[gi] = PriorIdxFinder(gi, FU_BRU_BIT);
      assign i_bru_iop = i_iop[i_bru_idx[gi]];

      wire i_bru_fire = i_bru_valid[gi] & i_bru_ready;
      wire o_bru_fire = o_bru_valid[gi] & o_bru_ready;
      reg_lr #(1) o_bru_valid_r (
          .load(i_bru_fire | o_bru_fire),
          .din (i_bru_fire),
          .dout(o_bru_valid[gi]),
          .*
      );
      reg_l #(IOP_SIZE) o_bru_iop_r (
          .load(i_bru_fire),
          .din (i_bru_iop),
          .dout(o_bru_iop[gi]),
          .*
      );
      //LSU
      assign i_lsu_valid[gi] = gi < dispatch_lsu_num;
      assign i_lsu_idx[gi] = PriorIdxFinder(gi, FU_LSU_BIT);
      assign i_lsu_iop = i_iop[i_lsu_idx[gi]];

      wire i_lsu_fire = i_lsu_valid[gi] & i_lsu_ready;
      wire o_lsu_fire = o_lsu_valid[gi] & o_lsu_ready;
      reg_lr #(1) o_lsu_valid_r (
          .load(i_lsu_fire | o_lsu_fire),
          .din (i_lsu_fire),
          .dout(o_lsu_valid[gi]),
          .*
      );
      reg_l #(IOP_SIZE) o_lsu_iop_r (
          .load(i_lsu_fire),
          .din (i_lsu_iop),
          .dout(o_lsu_iop[gi]),
          .*
      );
      //MDU
      assign i_mdu_valid[gi] = gi < dispatch_mdu_num;
      assign i_mdu_idx[gi] = PriorIdxFinder(gi, FU_MDU_BIT);
      assign i_mdu_iop = i_iop[i_mdu_idx[gi]];

      wire i_mdu_fire = i_mdu_valid[gi] & i_mdu_ready;
      wire o_mdu_fire = o_mdu_valid[gi] & o_mdu_ready;
      reg_lr #(1) o_mdu_valid_r (
          .load(i_mdu_fire | o_mdu_fire),
          .din (i_mdu_fire),
          .dout(o_mdu_valid[gi]),
          .*
      );
      reg_l #(IOP_SIZE) o_mdu_iop_r (
          .load(i_mdu_fire),
          .din (i_mdu_iop),
          .dout(o_mdu_iop[gi]),
          .*
      );
      //MISC
      assign i_misc_valid[gi] = gi < dispatch_misc_num;
      assign i_misc_idx[gi] = PriorIdxFinder(gi, FU_MISC_BIT);
      assign i_misc_iop = i_iop[i_misc_idx[gi]];

      wire i_misc_fire = i_misc_valid[gi] & i_misc_ready;
      wire o_misc_fire = o_misc_valid[gi] & o_misc_ready;
      reg_lr #(1) o_misc_valid_r (
          .load(i_misc_fire | o_misc_fire),
          .din (i_misc_fire),
          .dout(o_misc_valid[gi]),
          .*
      );
      reg_l #(IOP_SIZE) o_misc_iop_r (
          .load(i_misc_fire),
          .din (i_misc_iop),
          .dout(o_misc_iop[gi]),
          .*
      );
    end
    logic [DISPATCH_WIDTH-1:0] o_alu_valid_pack;
    logic [DISPATCH_WIDTH-1:0] o_bru_valid_pack;
    logic [DISPATCH_WIDTH-1:0] o_lsu_valid_pack;
    logic [DISPATCH_WIDTH-1:0] o_mdu_valid_pack;
    logic [DISPATCH_WIDTH-1:0] o_misc_valid_pack;
    assign i_alu_ready  = o_alu_ready | ~(|o_alu_valid_pack);
    assign i_bru_ready  = o_bru_ready | ~(|o_bru_valid_pack);
    assign i_lsu_ready  = o_lsu_ready | ~(|o_lsu_valid_pack);
    assign i_mdu_ready  = o_mdu_ready | ~(|o_mdu_valid_pack);
    assign i_misc_ready = o_misc_ready | ~(|o_misc_valid_pack);
    pack #(1, DISPATCH_WIDTH) pack_o_alu_valid (
        .din (o_alu_valid),
        .dout(o_alu_valid_pack)
    );
    pack #(1, DISPATCH_WIDTH) pack_o_bru_valid (
        .din (o_bru_valid),
        .dout(o_bru_valid_pack)
    );
    pack #(1, DISPATCH_WIDTH) pack_o_lsu_valid (
        .din (o_lsu_valid),
        .dout(o_lsu_valid_pack)
    );
    pack #(1, DISPATCH_WIDTH) pack_o_mdu_valid (
        .din (o_mdu_valid),
        .dout(o_mdu_valid_pack)
    );
    pack #(1, DISPATCH_WIDTH) pack_o_misc_valid (
        .din (o_misc_valid),
        .dout(o_misc_valid_pack)
    );
  endgenerate
  `PERF_EVENT(dispatch_busy_by_rob, ~o_rob_write_ready);
  `PERF_EVENT(dispatch_busy_by_execute, ~i_fu_ready);
  `PERF_EVENT(dispatch_busy_by_alu, ~i_alu_ready);
  `PERF_EVENT(dispatch_busy_by_bru, ~i_bru_ready);
  `PERF_EVENT(dispatch_busy_by_lsu, ~i_lsu_ready);
  `PERF_EVENT(dispatch_busy_by_mdu, ~i_mdu_ready);
  `PERF_EVENT(dispatch_busy_by_misc, ~i_misc_ready);
`ifdef DISPATCH_DIFFTEST
  `define ZEXT(width, val) {{(width-$size(val)){1'b0}},val}
  int id = 0;
  always_ff @(posedge clk) begin
    integer i;
    if (rst) begin
      id <= dispatch_create(id, $sformatf("%m"), DISPATCH_WIDTH);
    end else begin
      if (o_alu_ready) begin
        for (i = 0; i < DISPATCH_WIDTH; i = i + 1) begin
          if (o_alu_valid[i])
            dispatch_out(
            id,
            `ZEXT(8, FU_ALU),
            `ZEXT(8, o_alu_iop[i].rop.fop.dop.fu_type),
                         `ZEXT(32, o_alu_iop[i].rob_idx), o_alu_iop[i].rop.fop.pc);
        end
      end
      if (o_bru_ready) begin
        for (i = 0; i < DISPATCH_WIDTH; i = i + 1) begin
          if (o_bru_valid[i])
            dispatch_out(
            id,
            `ZEXT(8, FU_BRU),
            `ZEXT(8, o_bru_iop[i].rop.fop.dop.fu_type),
                         `ZEXT(32, o_bru_iop[i].rob_idx), o_bru_iop[i].rop.fop.pc);
        end
      end
      if (o_lsu_ready) begin
        for (i = 0; i < DISPATCH_WIDTH; i = i + 1) begin
          if (o_lsu_valid[i])
            dispatch_out(
            id,
            `ZEXT(8, FU_LSU),
            `ZEXT(8, o_lsu_iop[i].rop.fop.dop.fu_type),
                         `ZEXT(32, o_lsu_iop[i].rob_idx), o_lsu_iop[i].rop.fop.pc);
        end
      end
      if (o_mdu_ready) begin
        for (i = 0; i < DISPATCH_WIDTH; i = i + 1) begin
          if (o_mdu_valid[i])
            dispatch_out(
            id,
            `ZEXT(8, FU_MDU),
            `ZEXT(8, o_mdu_iop[i].rop.fop.dop.fu_type),
                         `ZEXT(32, o_mdu_iop[i].rob_idx), o_mdu_iop[i].rop.fop.pc);
        end
      end
      if (o_misc_ready) begin
        for (i = 0; i < DISPATCH_WIDTH; i = i + 1) begin
          if (o_misc_valid[i])
            dispatch_out(
            id,
            `ZEXT(8, FU_MISC),
            `ZEXT(8, o_misc_iop[i].rop.fop.dop.fu_type),
                         `ZEXT(32, o_misc_iop[i].rob_idx), o_misc_iop[i].rop.fop.pc);
        end
      end
      if (i_fire) begin
        for (i = 0; i < DISPATCH_WIDTH; i = i + 1) begin
          if (i_rop_valid[i]) begin
            dispatch_in(
            id,
            `ZEXT(8, i_rop[i].fop.dop.fu_type),
            `ZEXT(32, o_rob_write_rob_idx[i]),
                        i_rop[i].fop.pc);
          end
        end
      end
    end
  end
`endif
endmodule
